#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
@FileName: py_collection.py
@Version: 1.0
@Date: 2023/8/21 9:20
@Description:
"""

from run_test import rtest, print_sep
from collections import Counter
from collections import deque
from collections import OrderedDict
from collections import namedtuple
import re


def te1():
    # 计算top10的单词
    text = 'remove an existing key one level down remove an existing key one level down'
    words = re.findall(r'\w+', text)
    print(f'words: {words}')
    ret = Counter(words).most_common(5)
    print(f'most common 5: {ret}')

    print_sep()
    # 计算列表中单词的个数
    cnt = Counter()
    for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:
        cnt[word] += 1
    print(f'count list: {cnt}')

    print_sep()
    # 上述这样计算有点嘛，下面的方法更简单，直接计算就行
    L = ['red', 'blue', 'red', 'green', 'blue', 'blue']
    ret = Counter(L)
    print(f'count list2: {ret}')

    # 清楚记录 更新记录
    print_sep()
    L = ['red', 'blue', 'red', 'green', 'blue', 'blue']
    cnt = Counter(L)
    # cnt.clear()
    L2 = ['red', 'red', 'black', 'green', 'blue', 'blue']
    cnt.update(L2)
    print(f'update from list: {cnt}')

    print_sep()
    L = ['red', 'blue', 'red', 'green', 'blue', 'blue']
    cnt = Counter(L)
    L2 = {'red': 1, 'black': 2}
    cnt.update(L2)
    print(f'update from mapping: {cnt}')


def te2():
    # 字符串计数
    ret = Counter('gallahad')
    print(f'count string {ret}')

    # 字典计数
    ret = Counter({'red': 4, 'blue': 2})
    print(f'counter initialize {ret}')

    # 是个啥玩意计数
    ret = Counter(cats=4, dogs=8)
    print(f'counter initialize2 {ret}')

    ret = Counter(['red', 'blue', 'red', 'green', 'blue', 'blue'])
    print(f'counter from list {ret}')


def te2():
    c = Counter(a=4, b=2, c=0, d=5)
    d = c.elements()
    print(f'elements 2: {d} => {list(d)}')

    c = Counter(b=2, a=4, c=0, d=-2)
    d = c.elements()
    print(f'elements: {d} => {list(d)}')
    d = sorted(c.elements())
    print(f'sorted: {d}')


def te3():
    ret = Counter('abracadabra').most_common(3)
    print(f'most common 3: {ret}')
    ret = Counter('abracadabra').most_common(5)
    print(f'most common 5: {ret}')


# subtract()
# 从迭代对象或映射对象减去元素。像dict.update() 但是是减去，而不是替换。输入和输出都可以是0或者负数。
def te4():
    c = Counter(a=4, b=2, c=0, d=-2)
    d = Counter(a=1, b=2, c=3, d=4)
    c.subtract(d)
    print(f'c - d: {c}')

    # 减去一个abcd
    str0 = Counter('aabbccdde')
    str0.subtract('abcd')
    print(f'str0 - abcd: {str0}')


# 字典方法
# 通常字典方法都可用于Counter对象，除了有两个方法工作方式与字典并不相同。

def te5():
    ret = dict.fromkeys(["a", "b", "c"], 0)
    print(f'build from list and default v: {ret}')
    ret = dict([('a', 1), ('b', 2), ('c', 3)])
    print(f'build from pairs {ret}')

    print_sep()
    c = Counter(b=2, a=4, c=0, d=-2)
    ret = sum(c.values())  # total of all counts
    print(f'sum all counts: {ret}')

    print_sep()
    c.clear()  # reset all counts
    c = Counter(b=6, a=5, c=4, d=-2)
    ret = list(c)  # list unique elements
    print(f'unique elements: {ret}')

    print_sep()
    ret = set(c)  # convert to a set
    print(f'convert to set: {ret}')

    print_sep()
    ret = dict(c)  # convert to a regular dictionary
    print(f'convert to dict: {ret}')

    print_sep()
    ret = c.items()  # convert to a list of (elem, cnt) pairs
    print(f'convert to pair list: {ret}')

    print_sep()
    list_of_pairs = [('a', 1), ('b', 2), ('c', 3), ('d', 8), ('e', 10), ('f', 7)]
    c = Counter(dict(list_of_pairs))  # convert from a list of (elem, cnt) pairs
    print(f'from pair list: {c}')
    n = 3
    ret = c.most_common()[:-n - 1:-1]  # n least common elements
    print(f'least most n: {ret}')

    print_sep()
    c = Counter(b=6, a=5, c=0, d=-2)
    ret = +c  # remove zero and negative counts
    print(f'remove <=0: {ret}')


# 数学操作
# 这个功能非常强大，提供了几个数学操作，可以结合 Counter 对象，以生产 multisets (计数器中大于0的元素）。
# 加和减，结合计数器，通过加上或者减去元素的相应计数。交集和并集返回相应计数的最小或最大值。
# 每种操作都可以接受带符号的计数，但是输出会忽略掉结果为零或者小于零的计数。
def te6():
    c = Counter(a=3, b=1)
    d = Counter(a=1, b=2)
    print(f'c + d: {c + d}')  # add two counters together:  c[x] + d[x]
    print(f'c - d: {c - d}')  # subtract (keeping only positive counts)
    print(f'c & d: {c & d}')  # intersection:  min(c[x], d[x])
    print(f'c | d: {c | d}')  # union:  max(c[x], d[x])

    # 单目加和减（一元操作符）意思是 左操作数 为空
    print_sep()
    c = Counter(a=2, b=-4)
    print(f'+c: {+c}')
    print(f'-c: {-c}')


# 写一个计算文本相似的算法，加权相似

def str_sim(str_0, str_1, topn):
    topn = int(topn)
    collect0 = Counter(dict(Counter(str_0).most_common(topn)))
    collect1 = Counter(dict(Counter(str_1).most_common(topn)))
    intersec = collect0 & collect1
    union = collect0 | collect1
    sim = float(sum(intersec.values())) / float(sum(union.values()))
    return (sim)


def te7():
    str_0 = '定位手机定位汽车定位GPS定位人定位位置查询'
    str_1 = '导航定位手机定位汽车定位GPS定位人定位位置查询'

    ret = str_sim(str_0, str_1, 5)
    print(f'similarity: {ret}')


# 双向队列-deque
# 双端队列，可以快速的从另外一侧追加和推出对象,deque是一个双向链表，针对list连续的数据结构插入和删除进行优化。
# 它提供了两端都可以操作的序列，这表示在序列的前后你都可以执行添加或删除操作。
# 双向队列(deque)对象支持以下方法：

def te8():
    d = deque('ghi')
    d.append('j')
    print(f'append: {d}')

    d.appendleft('f')
    print(f'append left: {d}')

    d = deque('ghi')
    d.clear()
    print(f'clear: {d}')

    print_sep()
    x = deque('xiaoweuge')
    y = x.copy()
    print(f'copy: {y}')
    x.pop()
    x.pop()
    print('after pop:\n', f'x => {x}\n', f'y => {y}')

    print_sep()
    d = deque('aa bb cc dd a')
    ret = d.count('a')
    print(f'd.count: {ret}')

    print_sep()
    a = deque('abc')
    b = deque('cd')
    a.extend(b)
    print(f'extend: {a}')

    # 与append 的区别
    a = deque('abc')
    b = deque('cd')
    a.append(b)
    print(f'append deque: {a}')

    # 扩展deque的左侧，通过添加iterable参数中的元素。
    # 注意，左添加时，在结果中iterable参数中的顺序将被反过来添加。
    a = deque('abc')
    b = deque('cd')
    a.extendleft(b)
    print(f'extend left: {a}')

    # 返回x 在 deque 中的位置（在索引start之后，索引stop 之前）
    # 返回第一个匹配项，如果未找到则引发 ValueError。
    print_sep()
    d = deque('xiaoweuge')
    ret = d.index('w')
    print(f'index : {ret}')

    a = deque('abc')
    ret = a.insert(1, 'X')
    print(f'insert : {ret}')

    print_sep()
    d = deque('abdcdea')
    ret = d.pop()
    print(f'after pop : {ret}')
    ret = d.popleft()
    print(f'after popleft : {ret}')

    a = deque('abca')
    a.remove('a')
    print(f'after remove : {ret}')

    # 逆序排列
    print_sep()
    d = deque('ghi')  # 创建一个deque
    ret = list(reversed(d))
    print(f'reversed, list : {ret}')

    ret = deque(reversed(d))
    print(f'reversed, deque : {ret}')


def te9():
    """
    向右循环移动 n 步。 如果 n 是负数，就向左循环。
    如果deque不是空的，向右循环移动一步就等价于 d.appendleft(d.pop()) ， 向左循环一步就等价于 d.append(d.popleft()) 。
    """
    # 向右边挤一挤
    d = deque('ghijkl')
    d.rotate(1)
    print(f'rotate right: {d}')

    # 向左边挤一挤
    d.rotate(-1)
    print(f'rotate left: {d}')

    # 看一个更明显的
    x = deque('12345')
    print(f'x origin: {x}')
    x.rotate()
    print(f'x rotated: {x}')

    d = deque(['12', 'ab', 'cd'])
    d.rotate(1)
    print(f'rotate 1: {d}')


def te10():
    """
    maxlen; Deque的最大尺寸，如果没有限定的话就是 None 。
    """
    d = deque(maxlen=10)
    for i in range(20):
        d.append(i)

    print(f'maxlen limited deque: {d}')

    # 除了以上操作，deque还支持迭代、封存、len(d)、reversed(d)、copy.deepcopy(d)、copy.copy(d)、
    # 成员检测运算符 in 以及下标引用例如通过 d[0] 访问首个元素等。
    # 索引访问在两端的复杂度均为 O(1) 但在中间则会低至 O(n)。 如需快速随机访问，请改用列表。


def te10():
    d = deque('ghi')  # 创建一个deque
    for elem in d:
        print(elem.upper())

    # 右边删除
    print_sep()
    ret = d.pop()
    print(f'pop: {ret}')

    # 左边边删除
    ret = d.popleft()
    print(f'popleft: {ret}')

    # 看看还剩下啥
    ret = list(d)
    print(f'list deque: {ret}')

    # 成员检测
    ret = 'h' in d
    print(f'h in deque: {ret}')


def te11():
    """列表 的初始化"""
    a = [0]
    print(a)


# 有序字典-OrderedDict
# 有序词典就像常规词典一样，但有一些与排序操作相关的额外功能,popitem() 方法有不同的签名。
# 它接受一个可选参数来指定弹出哪个元素。move_to_end() 方法，可以有效地将元素移动到任一端。

def te12():
    """
    popitem(last=True)
    功能：有序字典的 popitem() 方法移除并返回一个 (key, value) 键值对。
    如果 last 值为真，则按 LIFO 后进先出的顺序返回键值对，
    否则就按 FIFO 先进先出的顺序返回键值对。
    """
    d = OrderedDict.fromkeys('abcde')
    print(f'origin : {d}')

    ret = d.popitem()
    print(f'popitem: {ret}')
    print(f'after pop last: {d}')

    print_sep()
    # last=False时，弹出第一个
    d = OrderedDict.fromkeys('abcde')
    ret = ''.join(d.keys())
    print(f'origin : {ret}')
    ret = d.popitem(last=False)
    print(f'pop first : {ret}')
    ret = ''.join(d.keys())
    print(f'after pop first : {ret}')


def te13():
    """
    move_to_end() 方法，可以有效地将元素移动到任一端。
    """
    d = OrderedDict.fromkeys('abcde')
    print(f'origin : {d}')
    d.move_to_end('b')
    print(f'after move b>>: {d}')
    ret = ''.join(d.keys())
    print(f'after move b>>: {ret}')

    print_sep()
    d.move_to_end('b', last=False)
    print(f'after move b<<: {d}')
    ret = ''.join(d.keys())
    print(f'after move b<<: {ret}')

    print_sep()
    d = OrderedDict.fromkeys('abcde', 114514)
    print(f'origin : {d}')
    ret = list(reversed(d))
    print(f'reversed: {ret}')


# 可命名元组-namedtuple
# 生成可以使用名字来访问元素内容的tuple子类，命名元组赋予每个位置一个含义，提供可读性和自文档性。
# 它们可以用于任何普通元组，并添加了通过名字获取值的能力，通过索引值也是可以的。
def te14():
    Fish = namedtuple('Fish', ['name', 'species', 'tank'])
    sammy = Fish('Sammy', 'shark', 'tank-a')
    print(sammy)
    print(f'species: {sammy.species}')
    print(f'@index 1: {sammy[1]}')
    print(f'as dict: {sammy._asdict()}')

    # 定义命名元组类：Point
    print_sep()
    Point = namedtuple('Point', ['x', 'y'])
    # 初始化Point对象，即可用位置参数，也可用命名参数
    p = Point(11, y=22)
    print(f'tuple Point: {p}')
    # 像普通元组一样用根据索引访问元素
    print(f'by index: {p[0] + p[1]}')
    # 执行元组解包，按元素的位置解包
    a, b = p
    print(f'unpacking: {a, b}')

    # 根据字段名访问各元素
    print(f'by name: {p.x + p.y}')


def te15():
    Point = namedtuple('Point', ['x', 'y'])
    t = [14, 55]
    p = Point._make(t)
    print(f'_make: {p}')

    print_sep()
    ret = p._asdict()
    print(f'_asdict: {ret}')

    print_sep()
    ret = p._replace(x=33)
    print(f'_replace: {ret}')


def te16():
    """
    _fields
    字符串元组列出了字段名。用于提醒和从现有元组创建一个新的命名元组类型。
    """
    Point = namedtuple('Point', ['x', 'y'])
    p = Point(11, 22)
    print(f'_fields: {p._fields}')
    Color = namedtuple('Color', 'red green blue')
    Pixel = namedtuple('Pixel', Point._fields + Color._fields)
    ret = Pixel(11, 22, 128, 255, 0)
    print(f'Pixel: {ret}')

    print_sep()
    # 默认值从右边开始，与函数参数的规则一致
    Account = namedtuple('Account', ['name', 'type', 'balance'], defaults=[1, 0])
    ret = Account._field_defaults
    print(f'_field_defaults: {ret}')

    a = Account('premium')
    print(f'Account: {a}')


def te17():
    """
    getattr()
    要获取这个名字域的值，使用 getattr() 函数 :
    """
    Point = namedtuple('Point', ['x', 'y'])
    p = Point(11, 22)
    ret = getattr(p, 'x')
    print(f'by getattr: {ret}')

    print_sep()
    d = {'x': 11, 'y': 22}
    ret = Point(**d)
    print(f'from dict: {ret}')


def te18():
    """
    因为一个命名元组是一个正常的Python类，它可以很容易的通过子类更改功能。
    这里是如何添加一个计算域和定宽输出打印格式:
    """

    class Point(namedtuple('Point', ['x', 'y'])):
        __slots__ = ()

        @property
        def hypot(self):
            return (self.x ** 2 + self.y ** 2) ** 0.5

        def __str__(self):
            return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)

    for p in Point(3, 4), Point(14, 5 / 7):
        print(p)


# ======================================================
if __name__ == '__main__':
    # rtest(te1)
    # rtest(te2)
    # rtest(te3)
    # rtest(te4)
    # rtest(te5)
    # rtest(te6)
    # rtest(te7)
    # rtest(te8)
    # rtest(te9)
    # rtest(te10)
    # rtest(te11)
    rtest(te12)
    rtest(te13)
    rtest(te14)
    rtest(te15)
    rtest(te16)
    rtest(te17)
    rtest(te18)
